/* httpd - small HTTP server
**
** Copyright � 1999,2000 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/

#define SERVER_SOFTWARE "Arsen's HTTPD"
#define SERVER_URL "arsenliu@live.cn"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netdb.h>

#include "data_exchange.h"

#define OS_Linux
#ifdef OS_Linux
#define ARCH "Linux"
#define HAVE_DAEMON
#define HAVE_WAITPID
#define HAVE_SENDFILE
#define HAVE_LINUX_SENDFILE
#include <signal.h>
#include <sys/select.h>
#endif /* OS_Linux */

#include "match.h"
#include "tdate_parse.h"

#ifdef HAVE_SENDFILE
#ifdef HAVE_LINUX_SENDFILE
#include <sys/sendfile.h>
#else
#include <sys/uio.h>
#endif
#endif

#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
/* (Linux's TCP_CORK is basically the same as BSD's TCP_NOPUSH.) */
#endif

#include <openssl/md5.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
#define USE_IPV6
#endif

#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif
#ifndef STDOUT_FILENO
#define STDOUT_FILENO 1
#endif
#ifndef STDERR_FILENO
#define STDERR_FILENO 2
#endif

#ifndef SIZE_T_MAX
#define SIZE_T_MAX 2147483647L
#endif

#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

/* Do overlapping strcpy safely, by using memmove. */
#define ol_strcpy(dst, src) memmove(dst, src, strlen(src) + 1)

#ifndef DEFAULT_HTTP_PORT
#define DEFAULT_HTTP_PORT 80
#endif
#ifndef DEFAULT_HTTPS_PORT
#define DEFAULT_HTTPS_PORT 443
#endif
#ifndef DEFAULT_CERTFILE
#define DEFAULT_CERTFILE "/var/cert/httpd.pem"
#endif
#ifndef CGI_NICE
#define CGI_NICE 10
#endif

#ifndef READ_TIMEOUT
#define READ_TIMEOUT 60
#endif
#ifndef WRITE_TIMEOUT
#define WRITE_TIMEOUT 300
#endif
#ifndef DEFAULT_CHARSET
#define DEFAULT_CHARSET "UTF-8"
#endif
#ifndef MAX_SEND_BUFFER_SIZE
#define MAX_SEND_BUFFER_SIZE 1048576
#endif

#define METHOD_UNKNOWN 0
#define METHOD_GET 1
#define METHOD_HEAD 2
#define METHOD_POST 3
#define METHOD_PUT 4
#define METHOD_DELETE 5
#define METHOD_TRACE 6

/* A multi-family sockaddr. */
typedef union
{
	struct sockaddr sa;
	struct sockaddr_in sa_in;
#ifdef USE_IPV6
	struct sockaddr_in6 sa_in6;
	struct sockaddr_storage sa_stor;
#endif /* USE_IPV6 */
} usockaddr;

static unsigned short port;
static char *url_pattern = NULL;
static int no_empty_referrers = 0;
static char *local_pattern = NULL;
static char *hostname = NULL;
static char hostname_buf[128];
static char *charset = DEFAULT_CHARSET;
static int listen4_fd, listen6_fd;
static int do_ssl = 0;
static char *certfile = DEFAULT_CERTFILE;
static char *cipher = NULL;
static SSL_CTX *ssl_ctx;

/* Request variables. */
static int conn_fd;
static SSL *ssl;
static usockaddr client_addr;
static int method;
static char *path;
static char *file;
static char *query;
static char *protocol;
static int status;
static off_t bytes;
static char *req_hostname;
static size_t content_length;
static char *content_type;
static char *cookie;
static char *host;
static time_t if_modified_since;
static char *referrer;
static char *useragent;

static char *request;
static size_t request_size, request_len, request_idx;
static char *response;
static size_t response_size, response_len;

struct auth_handler
{
	char *pattern;
	int auth;
};

static struct auth_handler auth_handles[] = {
	{"language.get", 0},
	{"md5pwd.post", 0},
	{"login.html", 0},
	{"**.css", 0},
	{"**.ico", 0},
	{"**.jpeg", 0},
	{"**.jpg", 0},
	{"**.gif", 0},
	{"**.png", 0},
	{"**.js", 0},
	{"**.svg", 0},
	{"**.ttf", 0},
	{"**.woff", 0},
	{"**.woff2", 0},
	{"**.eot", 0},

	{"**.get", 1},
	{"**.post", 1},
	{"**.html", 1},
	{"**.cgi", 1},
	{"**.conf", 1},
	{"**.xml", 1},
	{"**.pcap", 1},
	{"**.csv", 1},
};

#include <semaphore.h>
#define WEB_SESSIONID_FILE "/var/web.cid"
static sem_t et_sem;
static int set_effective_time(const char *value)
{
	int fd;

	sem_wait(&et_sem);
	fd = open(WEB_SESSIONID_FILE, O_WRONLY | O_CREAT, S_IRWXU);
	if (fd == -1)
	{
		perror("open");
		return -1;
	}

	write(fd, value, strlen(value));

	close(fd);
	sem_post(&et_sem);
	return 0;
}

static int get_effective_time(char *value)
{
	int fd;

	if (!value)
		return -1;

	sem_wait(&et_sem);
	fd = open(WEB_SESSIONID_FILE, O_RDONLY, S_IRWXU);
	if (fd == -1)
	{
		perror("open");
		return -1;
	}

	read(fd, value, 32);

	close(fd);
	sem_post(&et_sem);
	return 0;
}

/* Set NDELAY mode on a socket. */
static void set_ndelay(int fd)
{
	int flags, newflags;

	flags = fcntl(fd, F_GETFL, 0);
	if (flags != -1)
	{
		newflags = flags | (int)O_NDELAY;
		if (newflags != flags)
			fcntl(fd, F_SETFL, newflags);
	}
}

/* Clear NDELAY mode on a socket. */
static void clear_ndelay(int fd)
{
	int flags, newflags;

	flags = fcntl(fd, F_GETFL, 0);
	if (flags != -1)
	{
		newflags = flags & ~(int)O_NDELAY;
		if (newflags != flags)
			fcntl(fd, F_SETFL, newflags);
	}
}

static void *e_malloc(size_t size)
{
	void *ptr;

	ptr = malloc(size);
	if (ptr == (void *)0)
	{
		fprintf(stderr, "httpd: out of memory\n");
		exit(1);
	}
	return ptr;
}

static void *e_realloc(void *optr, size_t size)
{
	void *ptr;

	ptr = realloc(optr, size);
	if (ptr == (void *)0)
	{
		fprintf(stderr, "httpd: out of memory\n");
		exit(1);
	}
	return ptr;
}

static char *e_strdup(char *ostr)
{
	char *str;

	str = strdup(ostr);
	if (str == NULL)
	{
		fprintf(stderr, "httpd: out of memory copying a string\n");
		exit(1);
	}
	return str;
}

#ifdef NO_SNPRINTF
/* Some systems don't have snprintf(), so we make our own that uses
** vsprintf().  This workaround is probably vulnerable to buffer overruns,
** so upgrade your OS!
*/
static int snprintf(char *str, size_t size, const char *format, ...)
{
	va_list ap;
	int r;

	va_start(ap, format);
	r = vsprintf(str, format, ap);
	va_end(ap);
	return r;
}
#endif /* NO_SNPRINTF */

static void add_data(char **bufP, size_t *bufsizeP, size_t *buflenP, const char *str, size_t len)
{
	if (*bufsizeP == 0)
	{
		*bufsizeP = len + 512;
		*buflenP = 0;
		*bufP = (char *)e_malloc(*bufsizeP);
	}
	else if (*buflenP + len >= *bufsizeP) /* allow for NUL */
	{
		*bufsizeP = *buflenP + len + 512;
		*bufP = (char *)e_realloc((void *)*bufP, *bufsizeP);
	}

	if (len > 0)
	{
		memmove(&((*bufP)[*buflenP]), str, len);
		*buflenP += len;
	}

	(*bufP)[*buflenP] = '\0';
}

static void add_str(char **bufP, size_t *bufsizeP, size_t *buflenP, const char *str)
{
	size_t len;

	if (str == NULL)
		len = 0;
	else
		len = strlen(str);
	add_data(bufP, bufsizeP, buflenP, str, len);
}

static ssize_t my_read(char *buf, size_t size)
{
	if (do_ssl)
		return SSL_read(ssl, buf, size);
	else
		return read(conn_fd, buf, size);
}

static ssize_t my_write(void *buf, size_t size)
{
	if (do_ssl)
		return SSL_write(ssl, buf, size);
	else
		return write(conn_fd, buf, size);
}

#ifdef HAVE_SENDFILE
static ssize_t my_sendfile(int fd, int s, off_t offset, size_t nbytes)
{
#ifdef HAVE_LINUX_SENDFILE
	off_t lo = offset;
	return sendfile(s, fd, &lo, nbytes);
#else  /* HAVE_LINUX_SENDFILE */
	int r;
	r = sendfile(fd, s, offset, nbytes, (struct sf_hdtr *)0, (off_t *)0, 0);
	if (r == 0)
		return nbytes;
	else
		return r;
#endif /* HAVE_LINUX_SENDFILE */
}
#endif /* HAVE_SENDFILE */

static void start_response(void)
{
	response_size = 0;
}

static void add_to_response(const char *str)
{
	add_str(&response, &response_size, &response_len, str);
}

static void send_response(void)
{
	my_write(response, response_len);
}

static void start_request(void)
{
	request_size = 0;
	request_idx = 0;
}

static void add_to_request(char *str, size_t len)
{
	add_data(&request, &request_size, &request_len, str, len);
}

static char *get_request_line(void)
{
	int i;
	char c;

	for (i = request_idx; request_idx < request_len; ++request_idx)
	{
		c = request[request_idx];
		if (c == '\012' || c == '\015')
		{
			request[request_idx] = '\0';
			++request_idx;
			if (c == '\015' && request_idx < request_len &&
				request[request_idx] == '\012')
			{
				request[request_idx] = '\0';
				++request_idx;
			}
			return &(request[i]);
		}
	}
	return NULL;
}

static void send_via_write(int fd, off_t size)
{
	/* On some systems an off_t is 64 bits while a size_t is still only
	** 32 bits.  The mmap() system call takes a size_t as the length argument,
	** so we can only use mmap() if the size will fit into a size_t.
	*/
	if (size <= SIZE_T_MAX)
	{
		size_t size_size = (size_t)size;
		unsigned char *ptr = mmap(0, size_size, PROT_READ, MAP_PRIVATE, fd, 0);
		if (ptr != (unsigned char *)-1)
		{
			unsigned char *p = ptr;
			size_t remaining_size = size_size;
#ifdef MADV_SEQUENTIAL
			/* If we have madvise, might as well call it.  Although sequential
			** access is probably already the default.
			*/
			(void)madvise(ptr, size_size, MADV_SEQUENTIAL);
#endif /* MADV_SEQUENTIAL */
			/* We could send the whole file in a single write, but if
			** it's huge then we run the risk of hitting the timeout.
			** So we do a loop writing large segments, and reseting the
			** timeout each time through.
			*/
			while (remaining_size > 0)
			{
				size_t buf_size = MIN(remaining_size, MAX_SEND_BUFFER_SIZE);
				ssize_t r = my_write(p, buf_size);
				if (r < 0 && (errno == EINTR || errno == EAGAIN))
				{
					sleep(1);
					continue;
				}
				if (r != buf_size)
					return;
				remaining_size -= r;
				p += r;
				alarm(WRITE_TIMEOUT);
			}
			(void)munmap(ptr, size_size);
		}
	}
	else
	{
		/* The file is too big for mmap, so we'll send it via read & write
		** instead.  This would be less efficient for small files because
		** it bypasses the buffer cache, but with a file this huge the
		** cache will get blown anyway.
		*/
		char buf[30000];

		for (;;)
		{
			ssize_t r = read(fd, buf, sizeof(buf));
			if (r < 0 && (errno == EINTR || errno == EAGAIN))
			{
				sleep(1);
				continue;
			}
			if (r <= 0)
				return;
			for (;;)
			{
				ssize_t r2 = my_write(buf, r);
				if (r2 < 0 && (errno == EINTR || errno == EAGAIN))
				{
					sleep(1);
					continue;
				}
				if (r2 != r)
					return;
				break;
			}
			alarm(WRITE_TIMEOUT);
		}
	}
}

static void send_via_sendfile(int fd, int s, off_t size)
{
	/* We could send the whole file in a single sendfile, but if
	** it's huge then we run the risk of hitting the timeout.
	** So we do a loop writing large segments, and reseting the
	** timeout each time through.
	**
	** This also avoids the problem of using sendfile on a file larger
	** than 2GB, since each segment size will now fit into a size_t.
	*/
	off_t remaining_size = size;
	off_t off = 0;
	while (remaining_size > 0)
	{
		size_t buf_size = MIN(remaining_size, MAX_SEND_BUFFER_SIZE);
		ssize_t r = my_sendfile(fd, s, off, buf_size);
		if (r < 0 && (errno == EINTR || errno == EAGAIN))
		{
			sleep(1);
			continue;
		}
		if (r != buf_size)
			return;
		remaining_size -= r;
		off += r;
		alarm(WRITE_TIMEOUT);
	}
}

static int sockaddr_check(usockaddr *usaP)
{
	switch (usaP->sa.sa_family)
	{
	case AF_INET:
		return 1;
#ifdef USE_IPV6
	case AF_INET6:
		return 1;
#endif /* USE_IPV6 */
	default:
		return 0;
	}
}

static size_t sockaddr_len(usockaddr *usaP)
{
	switch (usaP->sa.sa_family)
	{
	case AF_INET:
		return sizeof(struct sockaddr_in);
#ifdef USE_IPV6
	case AF_INET6:
		return sizeof(struct sockaddr_in6);
#endif /* USE_IPV6 */
	default:
		return 0; /* shouldn't happen */
	}
}

static char *ntoa(usockaddr *usaP)
{
#ifdef USE_IPV6
	static char str[200];

	if (getnameinfo(&usaP->sa, sockaddr_len(usaP), str, sizeof(str), 0, 0, NI_NUMERICHOST) != 0)
	{
		str[0] = '?';
		str[1] = '\0';
	}
	else if (IN6_IS_ADDR_V4MAPPED(&usaP->sa_in6.sin6_addr) && strncmp(str, "::ffff:", 7) == 0)
		/* Elide IPv6ish prefix for IPv4 addresses. */
		ol_strcpy(str, &str[7]);

	return str;

#else  /* USE_IPV6 */
	return inet_ntoa(usaP->sa_in.sin_addr);
#endif /* USE_IPV6 */
}

static int initialize_listen_socket(usockaddr *usaP)
{
	int listen_fd;
	int i;

	/* Check sockaddr. */
	if (!sockaddr_check(usaP))
	{
		fprintf(stderr, "httpd: unknown sockaddr family on listen socket - %d\n",
				usaP->sa.sa_family);
		return -1;
	}

	listen_fd = socket(usaP->sa.sa_family, SOCK_STREAM, 0);
	if (listen_fd < 0)
	{
		perror("socket");
		return -1;
	}

	fcntl(listen_fd, F_SETFD, 1);

	i = 1;
	if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i)) < 0)
	{
		perror("setsockopt SO_REUSEADDR");
		return -1;
	}

	if (bind(listen_fd, &usaP->sa, sockaddr_len(usaP)) < 0)
	{
		perror("bind");
		return -1;
	}

	if (listen(listen_fd, 1024) < 0)
	{
		perror("listen");
		return -1;
	}

#ifdef HAVE_ACCEPT_FILTERS
	{
		struct accept_filter_arg af;
		(void)bzero(&af, sizeof(af));
		strcpy(af.af_name, ACCEPT_FILTER_NAME);
		(void)setsockopt(listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, (char *)&af, sizeof(af));
	}
#endif /* HAVE_ACCEPT_FILTERS */

	return listen_fd;
}

static void add_headers(int s, const char *title, const char *extra_header, const char *me, const char *mt, off_t b, time_t mod)
{
	time_t now;
	char timebuf[100];
	char buf[10000];
	int s100;
	const char *rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT";

	status = s;
	bytes = b;
	start_response();
	snprintf(buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title);
	add_to_response(buf);
	snprintf(buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE);
	add_to_response(buf);
	now = time((time_t *)0);
	strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now));
	snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf);
	add_to_response(buf);
	s100 = status / 100;
	if (s100 != 2 && s100 != 3)
	{
		snprintf(buf, sizeof(buf), "Cache-Control: no-cache,no-store\015\012");
		add_to_response(buf);
	}
	if (extra_header != NULL && extra_header[0] != '\0')
	{
		snprintf(buf, sizeof(buf), "%s\015\012", extra_header);
		add_to_response(buf);
	}
	if (me != NULL && me[0] != '\0')
	{
		snprintf(buf, sizeof(buf), "Content-Encoding: %s\015\012", me);
		add_to_response(buf);
	}
	if (mt != NULL && mt[0] != '\0')
	{
		snprintf(buf, sizeof(buf), "Content-Type: %s\015\012", mt);
		add_to_response(buf);
	}
	if (bytes >= 0)
	{
		snprintf(buf, sizeof(buf), "Content-Length: %lld\015\012", (long long)bytes);
		add_to_response(buf);
	}
	if (mod != (time_t)-1)
	{
		strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&mod));
		snprintf(buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf);
		add_to_response(buf);
	}
	snprintf(buf, sizeof(buf), "Connection: close\015\012\015\012");
	add_to_response(buf);
}

static void finish_request(int exitstatus)
{
#undef LINGER_SOCKOPT
#define LINGER_READ

#define LINGER_SECS 5

#ifdef LINGER_SOCKOPT
	/* The sockopt version of lingering close. Doesn't actually work. */
	struct linger lin;

	shutdown(conn_fd, 1);
	lin.l_onoff = 1;
	lin.l_linger = LINGER_SECS;
	(void)setsockopt(
		conn_fd, SOL_SOCKET, SO_LINGER, (void *)&lin, sizeof(lin));
#endif /* LINGER_SOCKOPT */

#ifdef LINGER_READ
	/* The "non-blocking read until error/eof/timeout" version of
	** lingering close.
	*/
	int flags;
	fd_set rfds;
	struct timeval tv;
	int r;
	char *buf[1024];

	flags = fcntl(conn_fd, F_GETFL, 0);
	if (flags != -1)
	{
		flags |= O_NDELAY;
		fcntl(conn_fd, F_SETFL, flags);
	}
	shutdown(conn_fd, 1);
	for (;;)
	{
		FD_ZERO(&rfds);
		FD_SET(conn_fd, &rfds);
		tv.tv_sec = LINGER_SECS;
		tv.tv_usec = 0;
		r = select(conn_fd + 1, &rfds, (fd_set *)0, (fd_set *)0, &tv);
		if (r <= 0) /* timeout or error */
			break;
		r = read(conn_fd, (void *)buf, sizeof(buf));
		if (r <= 0) /* eof or error */
			break;
	}
#endif /* LINGER_READ */

	exit(exitstatus);
}

static void send_error_body(int s, char *title, char *text)
{
	char buf[1024];

	/* Send built-in error page. */
	snprintf(buf, sizeof(buf), "\
<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n\
\n\
<html>\n\
\n\
  <head>\n\
    <meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\">\n\
    <title>%d %s</title>\n\
  </head>\n\
\n\
  <body bgcolor=\"#cc9999\" text=\"#000000\" link=\"#2020ff\" vlink=\"#4040cc\">\n\
\n\
    <h4>%d %s</h4>\n",
			 s, title, s, title);
	add_to_response(buf);
	snprintf(buf, sizeof(buf), "%s\n", text);
	add_to_response(buf);
}

static void send_error_tail(void)
{
	char buf[512];

	if (match("**MSIE**", useragent))
	{
		int n;
		snprintf(buf, sizeof(buf), "<!--\n");
		add_to_response(buf);
		for (n = 0; n < 6; ++n)
		{
			snprintf(buf, sizeof(buf), "Padding so that MSIE deigns to show this error instead of its own canned one.\n");
			add_to_response(buf);
		}
		snprintf(buf, sizeof(buf), "-->\n");
		add_to_response(buf);
	}

	snprintf(buf, sizeof(buf), "\
    <hr>\n\
\n\
    <address><a href=\"%s\">%s</a></address>\n\
\n\
  </body>\n\
\n\
</html>\n",
			 SERVER_URL, SERVER_SOFTWARE);
	add_to_response(buf);
}

static void send_error(int s, char *title, char *extra_header, char *text)
{
	add_headers(s, title, extra_header, "", "text/html; charset=%s", (off_t)-1, (time_t)-1);
	send_error_body(s, title, text);
	send_error_tail();
	send_response();

	SSL_free(ssl);
	finish_request(1);
}

static void handle_read_timeout(int sig)
{
	send_error(408, "Request Timeout", "",
			   "No request appeared within a reasonable time period.");
}

struct mime_entry
{
	char *ext;
	size_t ext_len;
	char *val;
	size_t val_len;
};
static struct mime_entry enc_tab[] = {
#include "mime_encodings.h"
};
static const int n_enc_tab = sizeof(enc_tab) / sizeof(*enc_tab);
static struct mime_entry typ_tab[] = {
#include "mime_types.h"
};
static const int n_typ_tab = sizeof(typ_tab) / sizeof(*typ_tab);

#if 0

/* Base-64 decoding.  This represents binary data as printable ASCII
** characters.  Three 8-bit binary bytes are turned into four 6-bit
** values, like so:
**
**   [11111111]  [22222222]  [33333333]
**
**   [111111] [112222] [222233] [333333]
**
** Then the 6-bit values are represented using the characters "A-Za-z0-9+/".
*/

static int b64_decode_table[256] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0F */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1F */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2F */
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 30-3F */
	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,			/* 40-4F */
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5F */
	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6F */
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7F */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8F */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9F */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0-AF */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0-BF */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0-CF */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0-DF */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0-EF */
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1	/* F0-FF */
};

/* Do base-64 decoding on a string.  Ignore any non-base64 bytes.
** Return the actual number of bytes generated.  The decoded size will
** be at most 3/4 the size of the encoded, and may be smaller if there
** are padding characters (blanks, newlines).
*/
static int b64_decode(const char *str, unsigned char *space, int size)
{
	const char *cp;
	int space_idx, phase;
	int d, prev_d = 0;
	unsigned char c;

	space_idx = 0;
	phase = 0;
	for (cp = str; *cp != '\0'; ++cp)
	{
		d = b64_decode_table[(int)((unsigned char)*cp)];
		if (d != -1)
		{
			switch (phase)
			{
			case 0:
				++phase;
				break;
			case 1:
				c = ((prev_d << 2) | ((d & 0x30) >> 4));
				if (space_idx < size)
					space[space_idx++] = c;
				++phase;
				break;
			case 2:
				c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2));
				if (space_idx < size)
					space[space_idx++] = c;
				++phase;
				break;
			case 3:
				c = (((prev_d & 0x03) << 6) | d);
				if (space_idx < size)
					space[space_idx++] = c;
				phase = 0;
				break;
			}
			prev_d = d;
		}
	}
	return space_idx;
}
#endif

/* Converts hexadecimal to decimal (character): */
static char hex2dec(char *what)
{
	char digit;

	digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
	digit *= 16;
	digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));

	return (digit);
}

static void unescape_url(char *url)
{
	int x, y, len;

	len = strlen(url);

	for (x = 0, y = 0; url[y]; x++, y++)
	{
		if ((url[x] = url[y]) == '%' &&
			y < (len - 2))
		{
			url[x] = hex2dec(&url[y + 1]);
			y += 2;
		}
	}
	url[x] = '\0';
}

static void url_decode(char *s)
{
	char *pstr = s;

	/* convert plus (+) to space (' ') */
	for (pstr = s;
		 pstr != NULL && *pstr != '\0';
		 pstr++)
		if (*pstr == '+')
			*pstr = ' ';

	unescape_url(s);
}

static int hexit(char c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	if (c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	return 0; /* shouldn't happen, we're guarded by isxdigit() */
}

/* Copies and decodes a string.  It's ok for from and to to be the
** same string.
*/
static void strdecode(char *to, char *from)
{
	for (; *from != '\0'; ++to, ++from)
	{
		if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
		{
			*to = hexit(from[1]) * 16 + hexit(from[2]);
			from += 2;
		}
		else
			*to = *from;
	}
	*to = '\0';
}

static void calc_md5(const char *data, char *md5)
{
	MD5_CTX ctx;
	unsigned char md[16];
	char buf[64] = {'\0'};
	char tmp[8] = {'\0'};
	int i;

	if (NULL == data || NULL == md5)
	{
		return;
	}

	MD5_Init(&ctx);
	MD5_Update(&ctx, data, strlen(data));
	MD5_Final(md, &ctx);

	for (i = 0; i < 16; i++)
	{
		sprintf(tmp, "%02X", md[i]);
		strcat(buf, tmp);
	}

	sprintf(md5, "%s", buf);
}

/* Figure out MIME encodings and type based on the filename.  Multiple
** encodings are separated by commas, and are listed in the order in
** which they were applied to the file.
*/
static const char *figure_mime(char *name, char *me, size_t me_size)
{
	char *prev_dot;
	char *dot;
	char *ext;
	int me_indexes[100], n_me_indexes;
	size_t ext_len, me_len;
	int i, top, bot, mid;
	int r;
	const char *default_type = "text/plain; charset=%s";
	const char *type;

	/* Peel off encoding extensions until there aren't any more. */
	n_me_indexes = 0;
	for (prev_dot = &name[strlen(name)];; prev_dot = dot)
	{
		for (dot = prev_dot - 1; dot >= name && *dot != '.'; --dot)
			;
		if (dot < name)
		{
			/* No dot found.  No more encoding extensions, and no type
			** extension either.
			*/
			type = default_type;
			goto done;
		}
		ext = dot + 1;
		ext_len = prev_dot - ext;
		/* Search the encodings table.  Linear search is fine here, there
		** are only a few entries.
		*/
		for (i = 0; i < n_enc_tab; ++i)
		{
			if (ext_len == enc_tab[i].ext_len && strncasecmp(ext, enc_tab[i].ext, ext_len) == 0)
			{
				if (n_me_indexes < sizeof(me_indexes) / sizeof(*me_indexes))
				{
					me_indexes[n_me_indexes] = i;
					++n_me_indexes;
				}
				goto next;
			}
		}
		/* No encoding extension found.  Break and look for a type extension. */
		break;

	next:;
	}

	/* Binary search for a matching type extension. */
	top = n_typ_tab - 1;
	bot = 0;
	while (top >= bot)
	{
		mid = (top + bot) / 2;
		r = strncasecmp(ext, typ_tab[mid].ext, ext_len);
		if (r < 0)
			top = mid - 1;
		else if (r > 0)
			bot = mid + 1;
		else if (ext_len < typ_tab[mid].ext_len)
			top = mid - 1;
		else if (ext_len > typ_tab[mid].ext_len)
			bot = mid + 1;
		else
		{
			type = typ_tab[mid].val;
			goto done;
		}
	}
	type = default_type;

done:

	/* The last thing we do is actually generate the mime-encoding header. */
	me[0] = '\0';
	me_len = 0;
	for (i = n_me_indexes - 1; i >= 0; --i)
	{
		if (me_len + enc_tab[me_indexes[i]].val_len + 1 < me_size)
		{
			if (me[0] != '\0')
			{
				strcpy(&me[me_len], ",");
				++me_len;
			}
			strcpy(&me[me_len], enc_tab[me_indexes[i]].val);
			me_len += enc_tab[me_indexes[i]].val_len;
		}
	}

	return type;
}

/* Returns 1 if ok to serve the url, 0 if not. */
static int really_check_referrer(void)
{
	char *cp1;
	char *cp2;
	char *cp3;
	char *refhost;
	char *lp;

	/* Check for an empty referrer. */
	if (referrer == NULL || referrer[0] == '\0' ||
		(cp1 = strstr(referrer, "//")) == NULL)
	{
		/* Disallow if we require a referrer and the url matches. */
		if (no_empty_referrers && match(url_pattern, path))
			return 0;
		/* Otherwise ok. */
		return 1;
	}

	/* Extract referrer host. */
	cp1 += 2;
	for (cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2)
		continue;
	refhost = (char *)e_malloc(cp2 - cp1 + 1);
	for (cp3 = refhost; cp1 < cp2; ++cp1, ++cp3)
		if (isupper(*cp1))
			*cp3 = tolower(*cp1);
		else
			*cp3 = *cp1;
	*cp3 = '\0';

	/* Local pattern? */
	if (local_pattern != NULL)
		lp = local_pattern;
	else
	{
		/* We are vhosting, use the hostname on this connection. */
		lp = req_hostname;
		if (lp == NULL)
			/* Oops, no hostname.  Maybe it's an old browser that
			** doesn't send a Host: header.  We could figure out
			** the default hostname for this IP address, but it's
			** not worth it for the few requests like this.
			*/
			return 1;
	}

	/* If the referrer host doesn't match the local host pattern, and
	** the URL does match the url pattern, it's an illegal reference.
	*/
	if (!match(lp, refhost) && match(url_pattern, path))
		return 0;
	/* Otherwise ok. */
	return 1;
}

/* Returns if it's ok to serve the url, otherwise generates an error
** and exits.
*/
static void check_referrer(void)
{
	char *cp;

	/* Are we doing referrer checking at all? */
	if (url_pattern == NULL)
		return;

	/* Is it ok? */
	if (really_check_referrer())
		return;

	/* Lose. */
	if (req_hostname != NULL)
		cp = req_hostname;
	else
		cp = hostname;
	if (cp == NULL)
		cp = "";

	send_error(403, "Forbidden", "", "You must supply a local referrer.");
}

static void handle_write_timeout(int sig)
{
	finish_request(1);
}

/* Special hack to deal with broken browsers that send a LF or CRLF
** after POST data, causing TCP resets - we just read and discard up
** to 2 bytes.  Unfortunately this doesn't fix the problem for CGIs
** which avoid the interposer process due to their POST data being
** short.  Creating an interposer process for all POST CGIs is
** unacceptably expensive.
*/
static void post_post_garbage_hack(void)
{
	char buf[2];

	if (do_ssl)
		/* We don't need to do this for SSL, since the garbage has
		** already been read.  Probably.
		*/
		return;

	set_ndelay(conn_fd);
	read(conn_fd, buf, sizeof(buf));
	clear_ndelay(conn_fd);
}

/* This routine is used only for POST requests.  It reads the data
** from the request and sends it to the child process.  The only reason
** we need to do it this way instead of just letting the child read
** directly is that we have already read part of the data into our
** buffer.
**
** Oh, and it's also used for all SSL CGIs.
*/
static void cgi_interpose_input(int wfd)
{
	size_t c;
	ssize_t r, r2;
	char buf[1024];

	/* Set up the timeout for reading again, since we're in a sub-process. */
#ifdef HAVE_SIGSET
	sigset(SIGALRM, handle_read_timeout);
#else  /* HAVE_SIGSET */
	signal(SIGALRM, handle_read_timeout);
#endif /* HAVE_SIGSET */
	alarm(READ_TIMEOUT);

	c = request_len - request_idx;
	if (c > 0)
	{
		if (write(wfd, &(request[request_idx]), c) != c)
			return;
	}
	while (c < content_length)
	{
		r = my_read(buf, MIN(sizeof(buf), content_length - c));
		if (r < 0 && (errno == EINTR || errno == EAGAIN))
		{
			sleep(1);
			continue;
		}
		if (r <= 0)
			return;
		for (;;)
		{
			r2 = write(wfd, buf, r);
			if (r2 < 0 && (errno == EINTR || errno == EAGAIN))
			{
				sleep(1);
				continue;
			}
			if (r2 != r)
				return;
			break;
		}
		c += r;
		alarm(READ_TIMEOUT);
	}
	post_post_garbage_hack();
}

/* This routine is used for parsed-header CGIs and for all SSL CGIs. */
static void cgi_interpose_output(int rfd, int parse_headers)
{
	ssize_t r, r2;
	char buf[1024];

	/* Set up the timeout for writing again, since we're in a sub-process. */
#ifdef HAVE_SIGSET
	sigset(SIGALRM, handle_write_timeout);
#else  /* HAVE_SIGSET */
	signal(SIGALRM, handle_write_timeout);
#endif /* HAVE_SIGSET */
	alarm(WRITE_TIMEOUT);

	if (!parse_headers)
	{
		/* If we're not parsing headers, write out the default status line
		** and proceed to the echo phase.
		*/
		char http_head[] = "HTTP/1.0 200 OK\015\012";
		my_write(http_head, sizeof(http_head));
	}
	else
	{
		/* Header parsing.  The idea here is that the CGI can return special
		** headers such as "Status:" and "Location:" which change the return
		** status of the response.  Since the return status has to be the very
		** first line written out, we have to accumulate all the headers
		** and check for the special ones before writing the status.  Then
		** we write out the saved headers and proceed to echo the rest of
		** the response.
		*/
		size_t headers_size, headers_len;
		char *headers;
		char *br;
		int s;
		char *title;
		char *cp;

		/* Slurp in all headers. */
		headers_size = 0;
		add_data(&headers, &headers_size, &headers_len, NULL, 0);
		for (;;)
		{
			r = read(rfd, buf, sizeof(buf));
			if (r < 0 && (errno == EINTR || errno == EAGAIN))
			{
				sleep(1);
				continue;
			}
			if (r <= 0)
			{
				br = &(headers[headers_len]);
				break;
			}
			add_data(&headers, &headers_size, &headers_len, buf, r);
			if ((br = strstr(headers, "\015\012\015\012")) != NULL ||
				(br = strstr(headers, "\012\012")) != NULL)
				break;
		}

		/* If there were no headers, bail. */
		if (headers_len == 0)
			return;

		/* Figure out the status. */
		s = 200;
		if ((cp = strstr(headers, "Location:")) != NULL &&
			cp < br &&
			(cp == headers || *(cp - 1) == '\012'))
			s = 302;
		if ((cp = strstr(headers, "Status:")) != NULL &&
			cp < br &&
			(cp == headers || *(cp - 1) == '\012'))
		{
			cp += 7;
			cp += strspn(cp, " \t");
			s = atoi(cp);
		}

		/* Write the status line. */
		switch (s)
		{
		case 200:
			title = "OK";
			break;
		case 302:
			title = "Found";
			break;
		case 304:
			title = "Not Modified";
			break;
		case 400:
			title = "Bad Request";
			break;
		case 401:
			title = "Unauthorized";
			break;
		case 403:
			title = "Forbidden";
			break;
		case 404:
			title = "Not Found";
			break;
		case 408:
			title = "Request Timeout";
			break;
		case 451:
			title = "Unavailable For Legal Reasons";
			break;
		case 500:
			title = "Internal Error";
			break;
		case 501:
			title = "Not Implemented";
			break;
		case 503:
			title = "Service Temporarily Overloaded";
			break;
		default:
			title = "Something";
			break;
		}
		snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", s, title);
		my_write(buf, strlen(buf));

		/* Write the saved headers. */
		my_write(headers, headers_len);
	}

	/* Echo the rest of the output. */
	for (;;)
	{
		r = read(rfd, buf, sizeof(buf));
		if (r < 0 && (errno == EINTR || errno == EAGAIN))
		{
			sleep(1);
			continue;
		}
		if (r <= 0)
			return;
		for (;;)
		{
			r2 = my_write(buf, r);
			if (r2 < 0 && (errno == EINTR || errno == EAGAIN))
			{
				sleep(1);
				continue;
			}
			if (r2 != r)
				return;
			break;
		}
		alarm(WRITE_TIMEOUT);
	}
}

static char *get_method_str(int m)
{
	switch (m)
	{
	case METHOD_GET:
		return "GET";
	case METHOD_HEAD:
		return "HEAD";
	case METHOD_POST:
		return "POST";
	case METHOD_PUT:
		return "PUT";
	case METHOD_DELETE:
		return "DELETE";
	case METHOD_TRACE:
		return "TRACE";
	default:
		return "UNKNOWN";
	}
}

static char *build_env(char *fmt, char *arg)
{
	char *cp;
	int size;
	static char *buf;
	static int maxbuf = 0;

	size = strlen(fmt) + strlen(arg);
	if (size > maxbuf)
	{
		if (maxbuf == 0)
		{
			maxbuf = MAX(200, size + 100);
			buf = (char *)e_malloc(maxbuf);
		}
		else
		{
			maxbuf = MAX(maxbuf * 2, size * 5 / 4);
			buf = (char *)e_realloc((void *)buf, maxbuf);
		}
	}
	snprintf(buf, maxbuf, fmt, arg);
	cp = e_strdup(buf);
	return cp;
}

/* Set up CGI environment variables. Be real careful here to avoid
** letting malicious clients overrun a buffer.  We don't have
** to worry about freeing stuff since we're a sub-process.
*/
static char **make_envp(void)
{
	static char *envp[50];
	int envn;
	char *cp;
	char buf[256];

	envn = 0;
	if (req_hostname != NULL && req_hostname[0] != '\0')
		cp = req_hostname;
	else if (host != NULL && host[0] != '\0')
		cp = host;
	else
		cp = hostname;
	if (cp != NULL)
		envp[envn++] = build_env("SERVER_NAME=%s", cp);
	envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
	envp[envn++] = "SERVER_PROTOCOL=HTTP/1.0";
	snprintf(buf, sizeof(buf), "%d", port);
	envp[envn++] = build_env("SERVER_PORT=%s", buf);
	envp[envn++] = build_env("REQUEST_METHOD=%s", get_method_str(method));
	envp[envn++] = build_env("SCRIPT_NAME=%s", path);
	if (query[0] != '\0')
		envp[envn++] = build_env("QUERY_STRING=%s", query);
	envp[envn++] = build_env("REMOTE_ADDR=%s", ntoa(&client_addr));
	if (referrer[0] != '\0')
	{
		envp[envn++] = build_env("HTTP_REFERER=%s", referrer);
		envp[envn++] = build_env("HTTP_REFERRER=%s", referrer);
	}
	if (useragent[0] != '\0')
		envp[envn++] = build_env("HTTP_USER_AGENT=%s", useragent);
	if (cookie != NULL)
		envp[envn++] = build_env("HTTP_COOKIE=%s", cookie);
	if (host != NULL)
		envp[envn++] = build_env("HTTP_HOST=%s", host);
	if (content_type != NULL)
		envp[envn++] = build_env("CONTENT_TYPE=%s", content_type);
	if (content_length != -1)
	{
		snprintf(
			buf, sizeof(buf), "%lu", (unsigned long)content_length);
		envp[envn++] = build_env("CONTENT_LENGTH=%s", buf);
	}

	envp[envn] = NULL;
	return envp;
}

/* Set up CGI argument vector.  We don't have to worry about freeing
** stuff since we're a sub-process.  This gets done after make_envp() because
** we scribble on query.
*/
static char **make_argp(void)
{
	char **argp;
	int argn;
	char *cp1;
	char *cp2;

	/* By allocating an arg slot for every character in the query, plus
	** one for the filename and one for the NULL, we are guaranteed to
	** have enough.  We could actually use strlen/2.
	*/
	argp = (char **)malloc((strlen(query) + 2) * sizeof(char *));
	if (argp == (char **)0)
		return (char **)0;

	argp[0] = strrchr(file, '/');
	if (argp[0] != NULL)
		++argp[0];
	else
		argp[0] = file;

	argn = 1;
	/* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html,
	** "The server should search the query information for a non-encoded =
	** character to determine if the command line is to be used, if it finds
	** one, the command line is not to be used."
	*/
	if (strchr(query, '=') == NULL)
	{
		for (cp1 = cp2 = query; *cp2 != '\0'; ++cp2)
		{
			if (*cp2 == '+')
			{
				*cp2 = '\0';
				strdecode(cp1, cp1);
				argp[argn++] = cp1;
				cp1 = cp2 + 1;
			}
		}
		if (cp2 != cp1)
		{
			strdecode(cp1, cp1);
			argp[argn++] = cp1;
		}
	}

	argp[argn] = NULL;
	return argp;
}

static void do_cgi(void)
{
	char **argp;
	char **envp;
	int parse_headers;
	char *binary;
	char *directory;

	/* If the socket happens to be using one of the stdin/stdout/stderr
	** descriptors, move it to another descriptor so that the dup2 calls
	** below don't screw things up.  We arbitrarily pick fd 3 - if there
	** was already something on it, we clobber it, but that doesn't matter
	** since at this point the only fd of interest is the connection.
	** All others will be closed on exec.
	*/
	if (conn_fd == STDIN_FILENO || conn_fd == STDOUT_FILENO || conn_fd == STDERR_FILENO)
	{
		int newfd = dup2(conn_fd, STDERR_FILENO + 1);
		if (newfd >= 0)
			conn_fd = newfd;
		/* If the dup2 fails, shrug.  We'll just take our chances.
		** Shouldn't happen though.
		*/
	}

	/* Make the environment vector. */
	envp = make_envp();

	/* Make the argument vector. */
	argp = make_argp();

	/* Set up stdin.  For POSTs we may have to set up a pipe from an
	** interposer process, depending on if we've read some of the data
	** into our buffer.  We also have to do this for all SSL CGIs.
	*/
	if ((method == METHOD_POST && request_len > request_idx) || do_ssl)
	{
		int p[2];
		int r;

		if (pipe(p) < 0)
			send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.");
		r = fork();
		if (r < 0)
			send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.");
		if (r == 0)
		{
			/* Interposer process. */
			close(p[0]);
			cgi_interpose_input(p[1]);
			finish_request(0);
		}
		close(p[1]);
		if (p[0] != STDIN_FILENO)
		{
			dup2(p[0], STDIN_FILENO);
			close(p[0]);
		}
	}
	else
	{
		/* Otherwise, the request socket is stdin. */
		if (conn_fd != STDIN_FILENO)
			dup2(conn_fd, STDIN_FILENO);
	}

	/* Set up stdout/stderr.  For SSL, or if we're doing CGI header parsing,
	** we need an output interposer too.
	*/
	if (strncmp(argp[0], "nph-", 4) == 0)
		parse_headers = 0;
	else
		parse_headers = 1;

	if (parse_headers || do_ssl)
	{
		int p[2];
		int r;

		if (pipe(p) < 0)
			send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.");
		r = fork();
		if (r < 0)
			send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.");
		if (r == 0)
		{
			/* Interposer process. */
			close(p[1]);
			cgi_interpose_output(p[0], parse_headers);
			finish_request(0);
		}
		close(p[0]);
		if (p[1] != STDOUT_FILENO)
			dup2(p[1], STDOUT_FILENO);
		if (p[1] != STDERR_FILENO)
			dup2(p[1], STDERR_FILENO);
		if (p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO)
			close(p[1]);
	}
	else
	{
		/* Otherwise, the request socket is stdout/stderr. */
		if (conn_fd != STDOUT_FILENO)
			dup2(conn_fd, STDOUT_FILENO);
		if (conn_fd != STDERR_FILENO)
			dup2(conn_fd, STDERR_FILENO);
	}

	/* At this point we would like to set conn_fd to be close-on-exec.
	** Unfortunately there seems to be a Linux problem here - if we
	** do this close-on-exec in Linux, the socket stays open but stderr
	** gets closed - the last fd duped from the socket.  What a mess.
	** So we'll just leave the socket as is, which under other OSs means
	** an extra file descriptor gets passed to the child process.  Since
	** the child probably already has that file open via stdin stdout
	** and/or stderr, this is not a problem.
	*/
	/* fcntl( conn_fd, F_SETFD, 1 ); */

	/* Set priority. */
	nice(CGI_NICE);

	/* Split the program into directory and binary, so we can chdir()
	** to the program's own directory.  This isn't in the CGI 1.1
	** spec, but it's what other HTTP servers do.
	*/
	directory = e_strdup(file);
	binary = strrchr(directory, '/');
	if (binary == NULL)
		binary = file;
	else
	{
		*binary++ = '\0';
		chdir(directory); /* ignore errors */
	}

	/* Default behavior for SIGPIPE. */
#ifdef HAVE_SIGSET
	sigset(SIGPIPE, SIG_DFL);
#else  /* HAVE_SIGSET */
	signal(SIGPIPE, SIG_DFL);
#endif /* HAVE_SIGSET */

	/* Run the program. */
	execve(binary, argp, envp);

	/* Something went wrong. */
	send_error(500, "Internal Error", "", "Something unexpected went wrong running a CGI program.");
}

static int check_session_cookie(void)
{
	char *delim = ";", *p, *req, *sep, *value = NULL;
	char mime_type[128], extra_header[128], client[16], server[16];

	req = cookie;
	while ((p = strtok(req, delim)))
	{
		req = NULL;
		if (p && ((sep = strchr(p, '='))))
		{
			if (strstr(p, "effective_time"))
			{
				value = sep + 1;
				break;
			}
		}
	}

	memset(client, 0x00, sizeof(client));
	memset(server, 0x00, sizeof(server));
	snprintf(client, sizeof(client), "%s", value);
	get_effective_time(server);
	printf("effective_time: Client<%s>, Server:<%s>\n", value, server);
	if (strcmp(client, server))
	{
		snprintf(mime_type, sizeof(mime_type), "text/html; charset=%s", charset);
		snprintf(extra_header, sizeof(extra_header), "Location:/login.html");
		add_headers(302, "Redirect", extra_header, "", mime_type, (off_t)-1, -1);
		send_response();
		return -1;
	}

	return 0;
}

static void web_postdata_md5pwd(void)
{
	char session[16], mime_type[128], resdata[512], md5pwd[64], password[16];
	char *req, *name, *val, *delim = "&", *p, *sep, *encoded = NULL, *nonce = NULL;
	char buff[128];
	time_t now;
	int code = 400;

	req = &request[request_idx];
	while ((p = strtok(req, delim)))
	{
		req = NULL;
		url_decode(p);
		if (p && (NULL != (sep = strchr(p, '='))))
		{
			name = p;
			val = sep + 1;
			*sep = '\0';
			if (!strcmp(name, "encoded"))
			{
				encoded = val;
			}
			else if (!strcmp(name, "nonce"))
			{
				nonce = val;
			}
		}
	}

	/* ST_WEB_PARAM *pstWebParam = CFG_GetWebParam(); */
	data_exchange_getpwd(password);

	snprintf(buff, 128, "%s:%s", password, nonce);
	calc_md5(buff, md5pwd);
	strcpy(session, "\"\"");
	/* printf("A:%s, S:%s, C:%s\n", buff, md5pwd, encoded); */
	if (!strcasecmp(md5pwd, encoded))
	{
		code = 200;
		now = time((time_t *)0);
		strftime(session, sizeof(session), "%Y%m%d%H%M%S", gmtime(&now));
		set_effective_time(session);
		printf("Change the effective_time to:%s\n", session);
	}

	snprintf(mime_type, sizeof(mime_type), "application/json; charset=%s", charset);
	snprintf(resdata, sizeof(resdata), "{\"ResponseCode\":%d,\"SessionId\":%s}", code, session);
	add_headers(200, "Ok", "", "", mime_type, (off_t)strlen(resdata), -1);
	add_to_response(resdata);
	send_response();
}

/*
static void web_getdata_language(void)
{
	char mime_type[128], resdata[128];

	snprintf(mime_type, sizeof(mime_type), "application/json; charset=%s", charset);
	snprintf(resdata, sizeof(resdata), "{\"Language\":\"EN\"}");
	add_headers(200, "Ok", "", "", mime_type, (off_t)strlen(resdata), -1);
	add_to_response(resdata);
	send_response();
}
*/

void web_response(int sta, const char *data)
{
	char mime_type[128];

	if (sta == 200)
	{
		snprintf(mime_type, sizeof(mime_type), "application/json; charset=%s", charset);
		add_headers(200, "Ok", "", "", mime_type, (off_t)strlen(data), -1);
		add_to_response(data);
		send_response();
	}
	else
		send_error(500, file, "Internal Error.", "Application processing returns a failure value.");
}

static void do_post(void)
{
	char buf[2 * 1024];
	size_t c;

	printf("do_post_file: %s\n", file);

	if (content_length > sizeof(buf))
	{
		/* The data is too long */
		post_post_garbage_hack();
		send_error(503, "Bad Request", "", "The data is too long.");
		return;
	}

	c = request_len - request_idx;
	while (c < content_length)
	{
		ssize_t rr = my_read(buf, MIN(sizeof(buf), content_length - c));
		if (rr < 0 && (errno == EINTR || errno == EAGAIN))
			continue;
		if (rr <= 0)
			break;
		alarm(READ_TIMEOUT);
		add_to_request(buf, rr);
	}

	if (!strcasecmp(file, "md5pwd.post"))
		web_postdata_md5pwd();
	/*
	else if (!strcasecmp(file, "language.get"))
		web_getdata_language();
	*/
	else if (0 > data_exchange(file, &request[request_idx], request_len - request_idx))
		send_error(451, file, "", "Unavailable For Legal Reasons.");
}

static void do_file(void)
{
	char mime_encodings[128], fixed_mime_type[128], filename[128];
	const char *mime_type;
	struct stat fstat;
	int fd;

	memset(filename, 0x00, sizeof(filename));
	snprintf(filename, 128, "/var/www/%s", file);
	printf("filename: %s\n", filename);

	if (0 > stat(filename, &fstat))
		send_error(404, "Not Found", "", "File not found.");

	/* Referrer check. */
	check_referrer();

	if (method != METHOD_GET)
		send_error(501, "Not Implemented", "", "That method is not implemented.");

	fd = open(filename, O_RDONLY);
	if (fd < 0)
		send_error(403, "Forbidden", "", "File is protected.");

	mime_type = figure_mime(filename, mime_encodings, sizeof(mime_encodings));
	snprintf(fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset);
	if (if_modified_since != (time_t)-1 && if_modified_since >= fstat.st_mtime)
	{
		add_headers(304, "Not Modified", "", mime_encodings, fixed_mime_type, (off_t)-1, fstat.st_mtime);
		send_response();
		return;
	}
	add_headers(200, "Ok", "", mime_encodings, fixed_mime_type, fstat.st_size, fstat.st_mtime);
	send_response();

	if (fstat.st_size > 0) /* ignore zero-length files */
	{
#ifdef HAVE_SENDFILE
		if (do_ssl)
			send_via_write(fd, fstat.st_size);
		else
			send_via_sendfile(fd, conn_fd, fstat.st_size);
#else  /* HAVE_SENDFILE */
		send_via_write(fd, fstat.st_size);
#endif /* HAVE_SENDFILE */
	}

	close(fd);
}

/* qsort comparison routine */
static int ext_compare(const void *v1, const void *v2)
{
	const struct mime_entry *m1 = (const struct mime_entry *)v1;
	const struct mime_entry *m2 = (const struct mime_entry *)v2;
	return strcmp(m1->ext, m2->ext);
}

static void init_mime(void)
{
	int i;

	/* Sort the tables so we can do binary search. */
	qsort(enc_tab, n_enc_tab, sizeof(*enc_tab), ext_compare);
	qsort(typ_tab, n_typ_tab, sizeof(*typ_tab), ext_compare);

	/* Fill in the lengths. */
	for (i = 0; i < n_enc_tab; ++i)
	{
		enc_tab[i].ext_len = strlen(enc_tab[i].ext);
		enc_tab[i].val_len = strlen(enc_tab[i].val);
	}
	for (i = 0; i < n_typ_tab; ++i)
	{
		typ_tab[i].ext_len = strlen(typ_tab[i].ext);
		typ_tab[i].val_len = strlen(typ_tab[i].val);
	}
}

static void handle_sigterm(int sig)
{
	/* Don't need to set up the handler again, since it's a one-shot. */
	fprintf(stderr, "httpd: exiting due to signal %d\n", sig);
	exit(1);
}

/* SIGHUP says to re-open the log file. */
static void handle_sighup(int sig)
{
	const int oerrno = errno;

#ifndef HAVE_SIGSET
	/* Set up handler again. */
	signal(SIGHUP, handle_sighup);
#endif /* ! HAVE_SIGSET */

	/* Restore previous errno. */
	errno = oerrno;
}

static void handle_sigchld(int sig)
{
	const int oerrno = errno;
	pid_t pid;
	int s;

#ifndef HAVE_SIGSET
	/* Set up handler again. */
	signal(SIGCHLD, handle_sigchld);
#endif /* ! HAVE_SIGSET */

	/* Reap defunct children until there aren't any more. */
	for (;;)
	{
#ifdef HAVE_WAITPID
		pid = waitpid((pid_t)-1, &s, WNOHANG);
#else					   /* HAVE_WAITPID */
		pid = wait3(&s, WNOHANG, (struct rusage *)0);
#endif					   /* HAVE_WAITPID */
		if ((int)pid == 0) /* none left */
			break;
		if ((int)pid < 0)
		{
			if (errno == EINTR || errno == EAGAIN)
				continue;
			/* ECHILD shouldn't happen with the WNOHANG option,
			** but with some kernels it does anyway.  Ignore it.
			*/
			if (errno != ECHILD)
			{
				perror("child wait");
			}
			break;
		}
	}

	/* Restore previous errno. */
	errno = oerrno;
}

static int lookup_hostname(usockaddr *usa4P, size_t sa4_len, int *gotv4P, usockaddr *usa6P, size_t sa6_len, int *gotv6P)
{
#ifdef USE_IPV6
	struct addrinfo hints;
	char portstr[10];
	int gaierr;
	struct addrinfo *ai;
	struct addrinfo *ai2;
	struct addrinfo *aiv6;
	struct addrinfo *aiv4;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM;
	snprintf(portstr, sizeof(portstr), "%d", port);
	if ((gaierr = getaddrinfo(hostname, portstr, &hints, &ai)) != 0)
	{
		printf("httpd: getaddrinfo %.80s - %s\n", hostname, gai_strerror(gaierr));
		return -1;
	}

	/* Find the first IPv6 and IPv4 entries. */
	aiv6 = (struct addrinfo *)0;
	aiv4 = (struct addrinfo *)0;
	for (ai2 = ai; ai2 != (struct addrinfo *)0; ai2 = ai2->ai_next)
	{
		switch (ai2->ai_family)
		{
		case AF_INET6:
			if (aiv6 == (struct addrinfo *)0)
				aiv6 = ai2;
			break;
		case AF_INET:
			if (aiv4 == (struct addrinfo *)0)
				aiv4 = ai2;
			break;
		}
	}

	if (aiv6 == (struct addrinfo *)0)
		*gotv6P = 0;
	else
	{
		if (sa6_len < aiv6->ai_addrlen)
		{
			printf("httpd: %.80s - sockaddr too small (%lu < %lu)\n",
				   hostname, (unsigned long)sa6_len, (unsigned long)aiv6->ai_addrlen);
			return -1;
		}
		memset(usa6P, 0, sa6_len);
		memmove(usa6P, aiv6->ai_addr, aiv6->ai_addrlen);
		*gotv6P = 1;
	}

	if (aiv4 == (struct addrinfo *)0)
		*gotv4P = 0;
	else
	{
		if (sa4_len < aiv4->ai_addrlen)
		{
			printf("httpd: %.80s - sockaddr too small (%lu < %lu)\n",
				   hostname, (unsigned long)sa4_len, (unsigned long)aiv4->ai_addrlen);
			return -1;
		}
		memset(usa4P, 0, sa4_len);
		memmove(usa4P, aiv4->ai_addr, aiv4->ai_addrlen);
		*gotv4P = 1;
	}

	freeaddrinfo(ai);

#else /* USE_IPV6 */

	struct hostent *he;
	*gotv6P = 0;

	memset(usa4P, 0, sa4_len);
	usa4P->sa.sa_family = AF_INET;
	if (hostname == NULL)
		usa4P->sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
	else
	{
		usa4P->sa_in.sin_addr.s_addr = inet_addr(hostname);
		if ((int)usa4P->sa_in.sin_addr.s_addr == -1)
		{
			he = gethostbyname(hostname);
			if (he == (struct hostent *)0)
			{
#ifdef HAVE_HSTRERROR
				printf("httpd: gethostbyname %.80s - %s\n", hostname, hstrerror(h_errno));
#else  /* HAVE_HSTRERROR */
				printf("httpd: gethostbyname %.80s failed\n", hostname);
#endif /* HAVE_HSTRERROR */
				return -1;
			}
			if (he->h_addrtype != AF_INET)
			{
				printf("httpd: %.80s - non-IP network address\n", hostname);
				return -1;
			}
			memmove(&usa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length);
		}
	}
	usa4P->sa_in.sin_port = htons(port);
	*gotv4P = 1;

#endif /* USE_IPV6 */
	return 0;
}

static void de_dotdot(char *f)
{
	char *cp;
	char *cp2;
	int l;

	/* Collapse any multiple / sequences. */
	while ((cp = strstr(f, "//")) != NULL)
	{
		for (cp2 = cp + 2; *cp2 == '/'; ++cp2)
			continue;
		ol_strcpy(cp + 1, cp2);
	}

	/* Remove leading ./ and any /./ sequences. */
	while (strncmp(f, "./", 2) == 0)
		ol_strcpy(f, f + 2);
	while ((cp = strstr(f, "/./")) != NULL)
		ol_strcpy(cp, cp + 2);

	/* Alternate between removing leading ../ and removing xxx/../ */
	for (;;)
	{
		while (strncmp(f, "../", 3) == 0)
			ol_strcpy(f, f + 3);
		cp = strstr(f, "/../");
		if (cp == NULL)
			break;
		for (cp2 = cp - 1; cp2 >= f && *cp2 != '/'; --cp2)
			continue;
		ol_strcpy(cp2 + 1, cp + 4);
	}

	/* Also elide any xxx/.. at the end. */
	while ((l = strlen(f)) > 3 &&
		   strcmp((cp = f + l - 3), "/..") == 0)
	{
		for (cp2 = cp - 1; cp2 >= f && *cp2 != '/'; --cp2)
			continue;
		if (cp2 < f)
			break;
		*cp2 = '\0';
	}
}

/* This runs in a child process, and exits when done, so cleanup is
** not needed.
*/
static void handle_request(void)
{
	char *method_str, *line, *cp;
	struct auth_handler *handler;
	int i;
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

	/* Set up the timeout for reading. */
#ifdef HAVE_SIGSET
	sigset(SIGALRM, handle_read_timeout);
#else  /* HAVE_SIGSET */
	signal(SIGALRM, handle_read_timeout);
#endif /* HAVE_SIGSET */
	alarm(READ_TIMEOUT);

	/* Initialize the request variables. */
	method = METHOD_UNKNOWN;
	path = NULL;
	file = NULL;
	query = "";
	protocol = NULL;
	status = 0;
	bytes = -1;
	req_hostname = NULL;
	content_type = NULL;
	content_length = -1;
	cookie = NULL;
	host = NULL;
	if_modified_since = (time_t)-1;
	referrer = "";
	useragent = "";

#ifdef TCP_NOPUSH
	if (!do_ssl)
	{
		/* Set the TCP_NOPUSH socket option, to try and avoid the 0.2 second
		** delay between sending the headers and sending the data.  A better
		** solution is writev() (as used in thttpd), or send the headers with
		** send(MSG_MORE) (only available in Linux so far).
		*/
		r = 1;
		(void)setsockopt(conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void *)&r, sizeof(r));
	}
#endif /* TCP_NOPUSH */

	if (do_ssl)
	{
		ssl = SSL_new(ssl_ctx);
		SSL_set_fd(ssl, conn_fd);
		if (SSL_accept(ssl) == 0)
		{
			ERR_print_errors_fp(stderr);
			finish_request(1);
		}
	}

	/* Read in the request. */
	start_request();
	for (;;)
	{
		char buf[50000];
		int rr = my_read(buf, sizeof(buf) - 1);
		if (rr < 0 && (errno == EINTR || errno == EAGAIN))
			continue;
		if (rr <= 0)
			break;
		alarm(READ_TIMEOUT);
		add_to_request(buf, rr);
		if (strstr(request, "\015\012\015\012") != NULL ||
			strstr(request, "\012\012") != NULL)
			break;
	}

	/* Parse the first line of the request. */
	method_str = get_request_line();
	if (method_str == NULL)
		send_error(400, "Bad Request", "", "Can't parse request.");
	path = strpbrk(method_str, " \t\012\015");
	if (path == NULL)
		send_error(400, "Bad Request", "", "Can't parse request.");
	*path++ = '\0';
	path += strspn(path, " \t\012\015");
	protocol = strpbrk(path, " \t\012\015");
	if (protocol == NULL)
		send_error(400, "Bad Request", "", "Can't parse request.");
	*protocol++ = '\0';
	protocol += strspn(protocol, " \t\012\015");
	query = strchr(path, '?');
	if (query == NULL)
		query = "";
	else
		*query++ = '\0';

	/* Parse the rest of the request headers. */
	while ((line = get_request_line()) != NULL)
	{
		if (line[0] == '\0')
			break;
		else if (strncasecmp(line, "Content-Length:", 15) == 0)
		{
			cp = &line[15];
			cp += strspn(cp, " \t");
			content_length = atol(cp);
		}
		else if (strncasecmp(line, "Content-Type:", 13) == 0)
		{
			cp = &line[13];
			cp += strspn(cp, " \t");
			content_type = cp;
		}
		else if (strncasecmp(line, "Cookie:", 7) == 0)
		{
			cp = &line[7];
			cp += strspn(cp, " \t");
			cookie = cp;
		}
		else if (strncasecmp(line, "Host:", 5) == 0)
		{
			cp = &line[5];
			cp += strspn(cp, " \t");
			host = cp;
			if (host[0] == '\0' || host[0] == '.' ||
				strchr(host, '/') != NULL)
				send_error(400, "Bad Request", "", "Can't parse request.");
		}
		else if (strncasecmp(line, "If-Modified-Since:", 18) == 0)
		{
			cp = &line[18];
			cp += strspn(cp, " \t");
			if_modified_since = tdate_parse(cp);
		}
		else if (strncasecmp(line, "Referer:", 8) == 0)
		{
			cp = &line[8];
			cp += strspn(cp, " \t");
			referrer = cp;
		}
		else if (strncasecmp(line, "Referrer:", 9) == 0)
		{
			cp = &line[9];
			cp += strspn(cp, " \t");
			referrer = cp;
		}
		else if (strncasecmp(line, "User-Agent:", 11) == 0)
		{
			cp = &line[11];
			cp += strspn(cp, " \t");
			useragent = cp;
		}
	}

	if (strcasecmp(method_str, get_method_str(METHOD_GET)) == 0)
		method = METHOD_GET;
	else if (strcasecmp(method_str, get_method_str(METHOD_POST)) == 0)
		method = METHOD_POST;
	/*
		else if (strcasecmp(method_str, get_method_str(METHOD_HEAD)) == 0)
			method = METHOD_HEAD;
		else if (strcasecmp(method_str, get_method_str(METHOD_PUT)) == 0)
			method = METHOD_PUT;
		else if (strcasecmp(method_str, get_method_str(METHOD_DELETE)) == 0)
			method = METHOD_DELETE;
		else if (strcasecmp(method_str, get_method_str(METHOD_TRACE)) == 0)
			method = METHOD_TRACE;
	*/
	else
		send_error(501, "Not Implemented", "", "That method is not implemented.");

	strdecode(path, path);
	if (path[0] != '/')
		send_error(400, "Bad Request", "", "Bad filename.");

	file = &(path[1]);
	de_dotdot(file);

	if (file[0] == '\0')
		file = "login.html";

	if (file[0] == '/' ||
		(file[0] == '.' && file[1] == '.' && (file[2] == '\0' || file[2] == '/')))
		send_error(400, "Bad Request", "", "Illegal filename.");

		/* Set up the timeout for writing. */
#ifdef HAVE_SIGSET
	sigset(SIGALRM, handle_write_timeout);
#else  /* HAVE_SIGSET */
	signal(SIGALRM, handle_write_timeout);
#endif /* HAVE_SIGSET */
	alarm(WRITE_TIMEOUT);

	for (i = 0; i < ARRAY_SIZE(auth_handles); i++)
	{
		handler = &auth_handles[i];
		/* use filename to match instead of use file */
		if (match(handler->pattern, file))
		{
			if (handler->auth && check_session_cookie())
			{
				goto FINISH_REQUEST;
			}
			break;
		}
	}

	if (method == METHOD_POST)
		do_post();
	else if ((method == METHOD_GET))
		do_file();
	else
		send_error(501, "Not Implemented", "", "That method is not implemented.");

FINISH_REQUEST:
	SSL_free(ssl);
	finish_request(0);
}

int main(void)
{
#define HTTPD_LOGON_TIMEOUT 300
	usockaddr host_addr4, host_addr6, usa;
	int gotv4, gotv6, maxfd, r;
	fd_set lfdset;
	socklen_t sz;
	struct timeval to;
	int nready;

	if (do_ssl)
		port = DEFAULT_HTTPS_PORT;
	else
		port = DEFAULT_HTTP_PORT;

	/* Look up hostname. */
	if (0 > lookup_hostname(&host_addr4, sizeof(host_addr4), &gotv4,
							&host_addr6, sizeof(host_addr6), &gotv6))
		return -1;

	if (hostname == NULL)
	{
		gethostname(hostname_buf, sizeof(hostname_buf));
		hostname = hostname_buf;
	}
	if (!(gotv4 || gotv6))
	{
		printf("httpd: can't find any valid address\n");
		return -1;
	}

	/* Initialize listen sockets.  Try v6 first because of a Linux peculiarity;
	** like some other systems, it has magical v6 sockets that also listen for
	** v4, but in Linux if you bind a v4 socket first then the v6 bind fails.
	*/
	if (gotv6)
		listen6_fd = initialize_listen_socket(&host_addr6);
	else
		listen6_fd = -1;
	if (gotv4)
		listen4_fd = initialize_listen_socket(&host_addr4);
	else
		listen4_fd = -1;
	/* If we didn't get any valid sockets, fail. */
	if (listen4_fd == -1 && listen6_fd == -1)
	{
		printf("httpd: can't bind to any address\n");
		return -1;
	}

	if (do_ssl)
	{
		SSL_load_error_strings();
		SSLeay_add_ssl_algorithms();
		ssl_ctx = SSL_CTX_new(SSLv23_server_method());
		SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
		if (certfile[0] != '\0')
			if (SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
				SSL_CTX_use_certificate_chain_file(ssl_ctx, certfile) == 0 ||
				SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
				SSL_CTX_check_private_key(ssl_ctx) == 0)
			{
				ERR_print_errors_fp(stderr);
				return -1;
			}
		if (cipher != NULL)
		{
			if (SSL_CTX_set_cipher_list(ssl_ctx, cipher) == 0)
			{
				ERR_print_errors_fp(stderr);
				return -1;
			}
		}
	}

	/* Make ourselves a daemon. */
#ifdef HAVE_DAEMON
	if (daemon(1, 1) < 0)
	{
		perror("daemon");
		exit(1);
	}
#endif

	/* Catch various signals. */
#ifdef HAVE_SIGSET
	sigset(SIGTERM, handle_sigterm);
	sigset(SIGINT, handle_sigterm);
	sigset(SIGUSR1, handle_sigterm);
	sigset(SIGHUP, handle_sighup);
	sigset(SIGCHLD, handle_sigchld);
	sigset(SIGPIPE, SIG_IGN);
#else  /* HAVE_SIGSET */
	signal(SIGTERM, handle_sigterm);
	signal(SIGINT, handle_sigterm);
	signal(SIGUSR1, handle_sigterm);
	signal(SIGHUP, handle_sighup);
	signal(SIGCHLD, handle_sigchld);
	signal(SIGPIPE, SIG_IGN);
#endif /* HAVE_SIGSET */

	data_exchange_init();
	init_mime();

	/* Main loop. */
	for (;;)
	{
		/* Do a select() on at least one and possibly two listen fds.
		** If there's only one listen fd then we could skip the select
		** and just do the (blocking) accept(), saving one system call;
		** that's what happened up through version 1.18.  However there
		** is one slight drawback to that method: the blocking accept()
		** is not interrupted by a signal call.  Since we definitely want
		** signals to interrupt a waiting server, we use select() even
		** if there's only one fd.
		*/
		FD_ZERO(&lfdset);
		maxfd = -1;
		if (listen4_fd != -1)
		{
			FD_SET(listen4_fd, &lfdset);
			if (listen4_fd > maxfd)
				maxfd = listen4_fd;
		}
		if (listen6_fd != -1)
		{
			FD_SET(listen6_fd, &lfdset);
			if (listen6_fd > maxfd)
				maxfd = listen6_fd;
		}
		to.tv_sec = HTTPD_LOGON_TIMEOUT;
		to.tv_usec = 0;
		nready = select(maxfd + 1, &lfdset, NULL, NULL, &to);
		if (nready == 0)
		{
			set_effective_time("$$$$$$$$$$$$$$");
			data_exchange_mqclear();
			continue;
		}
		else if (nready < 0)
		{
			if (errno == EINTR || errno == EAGAIN)
				continue; /* try again */
			perror("select");
			return -1;
		}

		/* Accept the new connection. */
		sz = sizeof(usa);
		if (listen4_fd != -1 && FD_ISSET(listen4_fd, &lfdset))
			conn_fd = accept(listen4_fd, &usa.sa, &sz);
		else if (listen6_fd != -1 && FD_ISSET(listen6_fd, &lfdset))
			conn_fd = accept(listen6_fd, &usa.sa, &sz);
		else
		{
			printf("httpd: select failure\n");
			return -1;
		}
		if (conn_fd < 0)
		{
			if (errno == EINTR || errno == EAGAIN || errno == ECONNABORTED)
				continue; /* try again */
#ifdef EPROTO
			if (errno == EPROTO)
				continue; /* try again */
#endif					  /* EPROTO */
			perror("accept");
			return -1;
		}

		/* Fork a sub-process to handle the connection. */
		r = fork();
		if (r < 0)
		{
			perror("fork");
			return -1;
		}
		if (r == 0)
		{
			/* Child process. */
			client_addr = usa;
			if (listen4_fd != -1)
				close(listen4_fd);
			if (listen6_fd != -1)
				close(listen6_fd);
			handle_request();
		}
		close(conn_fd);
	}
}
